package services

import (
	"bytes"
	"encoding/xml"
	"fmt"
	"github.com/robfig/cron/v3"
	"go.uber.org/zap"
	"io/ioutil"
	"net/http"
	. "neutron_probe/models/yAnalyser"
	. "neutron_probe/utils"
	"strconv"
	"strings"
	"time"
)

/*
*
调度服务调用链,把优先级不同的调度服务用责任链的方式链接起来。
排在前面的优先级高,如果前面的拒绝处理则调用后面的
*/
type FetchDevice4Dfmc struct {
	Crontab       string
	Cron          *cron.Cron
	Origin        string
	CrushingPlant string //破碎站名称
	AnalyserNum   string //中子仪编号
	Creator       string //公司名称
}

//// GetDataRequest 代表SOAP请求的结构，如果需要参数，请在这里定义
//type GetDataRequest struct {
//	XMLName xml.AnalyserNum `xml:"GetData"`
//}

// GetDataResponse 代表期望的SOAP响应结构，根据您提供的数据格式定义了所有字段
type GetDataResponse struct {
	DataTime string `xml:"NewDataSet>Table1>value"`
	Speed    string `xml:"NewDataSet>Table2>value"`
	Flux     string `xml:"NewDataSet>Table3>value"` //流量是瞬时的吨数,单位是每小时
	Load     string `xml:"NewDataSet>Table4>value"` //载荷是每米皮带上的物料吨数,单位是每米
	SiO2     string `xml:"NewDataSet>Table5>value"`
	Al2O3    string `xml:"NewDataSet>Table6>value"`
	Fe2O3    string `xml:"NewDataSet>Table7>value"`
	CaO      string `xml:"NewDataSet>Table8>value"`
	MgO      string `xml:"NewDataSet>Table9>value"`
	K2O      string `xml:"NewDataSet>Table10>value"`
	Na2O     string `xml:"NewDataSet>Table11>value"`
	SO3      string `xml:"NewDataSet>Table12>value"`
}

const FREE_UPDATE_SQL_WITH_PARAMS = "freeUpdateSqlWithParams"
const NEW_NETRON_DATA = "newData"

// fetchDataFromDevice 发送一个与curl命令匹配的POST请求
func (this *FetchDevice4Dfmc) fetchDataFromDevice() error {
	// 创建请求
	req, err := http.NewRequest("POST", fmt.Sprintf("%s/MyService.asmx/GetData", this.Origin), bytes.NewBuffer([]byte{}))
	if err != nil {
		return fmt.Errorf("error creating request: %w", err)
	}

	// 设置请求头
	req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7")
	req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6")
	req.Header.Set("Cache-Control", "max-age=0")
	req.Header.Set("Connection", "keep-alive")
	req.Header.Set("Content-Length", "0")
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Set("Origin", this.Origin)
	req.Header.Set("Referer", fmt.Sprintf("%s/MyService.asmx?op=GetData", this.Origin))
	req.Header.Set("Upgrade-Insecure-Requests", "1")
	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0")

	// 发送请求
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("error sending request: %w", err)
	}
	bodyBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("error reading response body: %w", err)
	}
	bodyString := string(bodyBytes)
	Logger.Info("fetchDataFromDevice", zap.String("resp", bodyString))
	if obj, err := this.ExtractAndConvertKeyValuePairs(bodyString); err == nil {
		err := GetDataSourceClient().updateData(NEW_NETRON_DATA, interface{}(obj))
		if err != nil {
			Logger.Info("failed to save Obj in db", zap.String("obj", Obj2JsonTxt(obj)), zap.Error(err))
			return err
		}
		Logger.Info("save Obj in db", zap.String("obj", Obj2JsonTxt(obj)))
		return nil
	} else {
		return err
	}
}

type NewDataSet struct {
	Table1 []Table1 `xml:"Table1"`
}
type Table1 struct {
	Id    string `xml:"id,attr"`
	Item  string `xml:"item"`
	Value string `xml:"value"`
}
type DataSet struct {
	Xmlns    string   `xml:"xmlns,attr"`
	Diffgram Diffgram `xml:"diffgram"`
}
type Diffgram struct {
	Msdata     string     `xml:"msdata,attr"`
	NewDataSet NewDataSet `xml:"NewDataSet"`
}

var MaterialMap = map[string]int{
	"CaO":   1,
	"MgO":   2,
	"Al2O3": 3,
	"K2O":   4,
	"SO3":   5,
	"SiO2":  6,
	"Fe2O3": 7,
	"Na2O":  8,
}

func AssignMaterialValue(fieldName string, y *YAnalyser, value string) {
	index, ok := MaterialMap[fieldName]
	if !ok {
		return
	}
	switch index {
	case 1:
		{
			y.Mat1 = ConvertToFloat64(value)
		}
	case 2:
		{
			y.Mat2 = ConvertToFloat64(value)
		}
	case 3:
		{
			y.Mat3 = ConvertToFloat64(value)
		}
	case 4:
		{
			y.Mat4 = ConvertToFloat64(value)
		}
	case 5:
		{
			y.Mat5 = ConvertToFloat64(value)
		}
	case 6:
		{
			y.Mat6 = ConvertToFloat64(value)
		}
	case 7:
		{
			y.Mat7 = ConvertToFloat64(value)
		}
	case 8:
		{
			y.Mat8 = ConvertToFloat64(value)
		}
	case 9:
		{
			y.Mat9 = ConvertToFloat64(value)
		}
	case 10:
		{
			y.Mat10 = ConvertToFloat64(value)
		}
	case 11:
		{
			y.Mat11 = ConvertToFloat64(value)
		}
	case 12:
		{
			y.Mat12 = ConvertToFloat64(value)
		}
	case 13:
		{
			y.Mat13 = ConvertToFloat64(value)

		}
	case 14:
		{
			y.Mat14 = ConvertToFloat64(value)
		}
	case 15:
		{
			y.Mat15 = ConvertToFloat64(value)
		}
	case 16:
		{
			y.Mat16 = ConvertToFloat64(value)
		}
	}
}

// ExtractAndConvertKeyValuePairs 从XML内容中提取键值对，并将"DataTime"转换为UTC Unix时间戳
func (this *FetchDevice4Dfmc) ExtractAndConvertKeyValuePairs(xmlContent string) (*YAnalyser, error) {
	var dataSet DataSet
	//有命名空间的不能解析所以要替换  diffgr:diffgram -> diffgr_diffgram
	// 将XML内容解析到结构体
	xmlContent = strings.ReplaceAll(xmlContent, "diffgr:diffgram", "diffgram")
	xmlContent = strings.ReplaceAll(xmlContent, "xmlns:msdata", "msdata")
	xmlContent = strings.ReplaceAll(xmlContent, "diffgr:id", "id")

	err := xml.Unmarshal([]byte(xmlContent), &dataSet)
	if err != nil {
		return nil, err
	}
	yAnalyser := YAnalyser{}
	for _, table1 := range dataSet.Diffgram.NewDataSet.Table1 {

		switch table1.Item {
		case "DataTime":
			{
				yAnalyser.TestAt = ConvertToUtc(table1.Value)
			}
		case "Speed": //皮带秤速度 米/秒
			{
				yAnalyser.Speed = ConvertToFloat64(table1.Value)
			}
		case "Flux": //皮带秤流量 吨/小时
			{
				yAnalyser.Flux = ConvertToFloat64(table1.Value)
			}
		case "Load": //皮带秤载荷 千克/米
			{
				yAnalyser.Load = ConvertToFloat64(table1.Value)

			}
		case "CaO", "MgO", "Al2O3", "K2O", "SO3", "SiO2", "Fe2O3", "Na2O":
			AssignMaterialValue(table1.Item, &yAnalyser, table1.Value)
		}
	}
	if yAnalyser.TestAt == nil ||
		yAnalyser.Flux == nil ||
		yAnalyser.Load == nil ||
		*yAnalyser.TestAt == 0 ||
		*yAnalyser.Flux == 0 ||
		*yAnalyser.Load == 0 {
		//说明未生产数据无效
		return nil, fmt.Errorf("no work")
	}

	//只有cao是必须存在的
	if yAnalyser.Mat1 == nil ||
		//yAnalyser.Mat2 == nil ||
		yAnalyser.Speed == nil ||
		*yAnalyser.Mat1 == 0 ||
		//*yAnalyser.Mat2 == 0 ||
		*yAnalyser.Speed == 0 {
		Logger.Error("", zap.String("data", Obj2JsonTxt(yAnalyser)))
		yAnalyser.Status = RefInt(0)
		return nil, fmt.Errorf("no mine")
	} else {
		yAnalyser.Status = RefInt(1)
	}
	yAnalyser.CrushingPlant = RefString(this.CrushingPlant)
	yAnalyser.AnalyserNum = RefString(this.AnalyserNum)
	yAnalyser.Token = RefString(fmt.Sprintf("%s_%d", this.AnalyserNum, *yAnalyser.TestAt))
	return &yAnalyser, nil
}

func ConvertToFloat64(value string) *float64 {
	floatValue, err := strconv.ParseFloat(value, 64)
	if err != nil {
		Logger.Error("ConvertToFloat64 failed to parse %s", zap.String("value", value), zap.Error(err))
		return nil
	}
	return RefFloat64(floatValue)
}

func ConvertToUtc(value string) *int {
	parsedTime, err := time.ParseInLocation("2006-01-02 15:04:05", value, time.Local)
	if err != nil {
		Logger.Error("ConvertToUtc failed to parse %s", zap.String("value", value), zap.Error(err))
		return nil
	}
	utcTimestamp := RefInt(int(parsedTime.UTC().Unix()))
	return utcTimestamp
}

// fetchDataFromDevice 发送SOAP请求并解析返回值
func (this *FetchDevice4Dfmc) FetchData() error {
	err := this.fetchDataFromDevice()
	if err != nil {
		Logger.Error("failed to FetchData:", zap.Error(err))
		return err
	}
	return nil
}

var fetchDevice4Dfmc *FetchDevice4Dfmc

func GetFetchDevice4Dfmc() *FetchDevice4Dfmc {
	return fetchDevice4Dfmc
}
func (this *FetchDevice4Dfmc) FetchDataRepeatedly() {
	Logger.Info("fetchDataFromDevice")
	this.FetchData()
	return
}
func StartFetchDevice4Dfmc(fetchDevice4DfmcVarNamePrex string) bool {
	fetchDevice4Dfmc = &FetchDevice4Dfmc{}
	if fetchDevice4Dfmc.Origin = GetConfig().String(fetchDevice4DfmcVarNamePrex+".origin", "http://192.168.110.133:8090"); len(fetchDevice4Dfmc.Origin) == 0 {
		Logger.Error("Failed to Start device of dongFangMeasurement&Control,for the param url empty!")
		return false
	}

	if fetchDevice4Dfmc.CrushingPlant = GetConfig().String(fetchDevice4DfmcVarNamePrex+".crushingPlant", "CP1"); len(fetchDevice4Dfmc.CrushingPlant) == 0 {
		Logger.Error("Failed to Start device of dongFangMeasurement&Control,for the param url empty!")
		return false
	}
	if fetchDevice4Dfmc.AnalyserNum = GetConfig().String(fetchDevice4DfmcVarNamePrex+".analyserNum", "DFMC1"); len(fetchDevice4Dfmc.AnalyserNum) == 0 {
		Logger.Error("Failed to Start device of dongFangMeasurement&Control,for the param url empty!")
		return false
	}

	if enabled := GetConfig().Int64(fetchDevice4DfmcVarNamePrex+".enabled", 0); enabled != 1 {
		Logger.Error("skip to Start device of dongFangMeasurement&Control.")
		return false
	}

	if fetchDevice4Dfmc.Crontab = GetConfig().String(fetchDevice4DfmcVarNamePrex+".crontab", "*/10 * * * *"); len(fetchDevice4Dfmc.Crontab) == 0 {
		Logger.Error("Failed to Start device of dongFangMeasurement&Control,for the param crontab error!")
		return false
	}
	fetchDevice4Dfmc.Cron = cron.New(cron.WithSeconds())
	if entityId, err := fetchDevice4Dfmc.Cron.AddFunc(fetchDevice4Dfmc.Crontab, fetchDevice4Dfmc.FetchDataRepeatedly); err != nil {
		Logger.Error("Failed to Start fetchDevice4Dfmc  for error!", zap.Error(err))
		return false
	} else {
		Logger.Info(fmt.Sprintf("Start fetchDevice4Dfmc with Id = %d", entityId))
	}

	fetchDevice4Dfmc.Cron.Start()
	return true
}
